<template>
  <div id="global-uploader">
    <div v-show="show"
      style="position: fixed; bottom: 10px;padding-left: 20px; line-height: 35px; right: 10px;height: 35px;width: 240px;background-color: #000000;opacity: 0.5;border-radius: 10px;color: #fff;">
      {{ str }}
    </div>
    <!-- 上传 -->
    <uploader ref="uploader" :options="options" :autoStart="true" @file-added="onFileAdded"
      @file-success="onFileSuccess" @file-progress="onFileProgress" @file-error="onFileError" class="uploader-app">
      <uploader-unsupport></uploader-unsupport>

      <uploader-btn id="global-uploader-btn" v-if="initBtn" :attrs="attrs" ref="uploadBtn">选择文件</uploader-btn>

      <uploader-list v-show="panelShow">
        <div class="file-panel" slot-scope="props" :class="{ collapse: collapse }">
          <div class="file-title">
            <div style="line-height: 40px">文件列表</div>
            <div class="operate">
              <el-button @click="fileListShow" type="text" :title="collapse ? '展开' : '折叠'">
                <i class="el-icon-d-caret fff" style="color: black; font-size: 18px"
                  :class="collapse ? 'inuc-fullscreen' : 'inuc-minus-round'"></i>
              </el-button>
              <el-button @click="close" type="text" title="关闭">
                <i class="el-icon-close fff" style="color: black; font-size: 18px"></i>
              </el-button>
            </div>
          </div>

          <ul class="file-list">
            <li v-for="file in props.fileList" :key="file.id">
              <uploader-file :class="'file_' + file.id" ref="files" :file="file" :list="true"></uploader-file>
            </li>
            <div class="no-file" v-if="!props.fileList.length">
              <i class="iconfont icon-empty-file"></i> 暂无待上传文件
            </div>
          </ul>
        </div>
      </uploader-list>
    </uploader>
  </div>
</template>

<script>
/**
 *   全局上传插件
 *   调用方法：Bus.$emit('openUploader', {}) 打开文件选择框，参数为需要传递的额外参数
 *   监听函数：Bus.$on('fileAdded', fn); 文件选择后的回调
 *            this.$EventBus.$on('fileSuccess', fn); 文件上传成功的回调
 */

import { ACCEPT_CONFIG } from "@/utils/tools/config";
import SparkMD5 from "spark-md5";
import { mergeFile } from "@/api/myspace/model-manage-tool/model-manage";
import $ from "jquery";
import { init } from "echarts";
export default {
  data() {
    return {
      show: false,
      str: "",
      params: null,
      options: {
        // 目标上传 URL
        target: process.env.VUE_APP_BASE_API + "/common/uploadShardedFile",
        //分块大小
        chunkSize: 50 * 1024 * 1000,
        //上传文件时文件的参数名，默认file
        fileParameterName: "file",
        //并发上传数
        simultaneousUploads: 1,
        //最大自动失败重试上传次数
        maxChunkRetries: 2,
        //重试间隔 单位毫秒
        //chunkRetryInterval: 5000,
        //是否开启服务器分片校验
        testChunks: true,
        // 服务器分片校验函数，秒传及断点续传基础
        checkChunkUploadedByResponse: function (chunk, message) {
          let objMessage = JSON.parse(message);
          if (objMessage.skipUpload) {
            return true;
          }
          return (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0;
        },
        headers: {
          Authorization: "",
        },
        // 额外的自定义查询参数
        query: {},
      },
      initBtn: false,//初始化按钮
      attrs: {
        accept: []
        // accept: [".gltf", ".glb"],
      },
      panelShow: false, //选择文件后，展示上传panel
      collapse: false,
      fileObject: {},
    };
  },
  mounted() {
    //接收子组件触发的事件
    this.$EventBus.$on("openUploader", (query) => {
      this.show = true;
      this.initBtn = false;
      this.params = query || {};
      this.options.headers.Authorization = "Bearer " + query.token;
      if (query.fileType) {
        // query.fileType.map(type => `${type}`).join(',')
        this.attrs.accept = query.fileType
      } else {
        this.attrs.accept = []
      }

      let _this = this
      setTimeout(() => {
        _this.initBtn = true;
      }, 100);
      setTimeout(() => {
        if (query.fileObject) {
          _this.customFileObject(query.fileObject);
        } else {
          if (_this.$refs.uploadBtn) {
            $("#global-uploader-btn").click();
          }
        }
      }, 200);

    });
    this.$EventBus.$on("close", (query) => {
      this.close();
    });
  },
  computed: {
    //Uploader实例
    uploader() {
      return this.$refs.uploader.uploader;
    },
  },
  methods: {
    customFileObject(file) {
      // 创建自定义对象
      const customFileObject = {
        isFolder: false,
        isRoot: false,
        id: this.generateUniqueIdentifier(file), // 可以根据需要生成唯一的ID
        file: file,
        fileType: file.type,
        name: file.name,
        size: file.size,
        relativePath: file.name,
        paused: false,
        error: false,
        allError: false,
        aborted: false,
        completed: false, // 初始状态为未完成
        averageSpeed: 0,
        currentSpeed: 0,
        _lastProgressCallback: Date.now(),
        _prevUploadedSize: 0,
        _prevProgress: 0,
        uniqueIdentifier: "", // md5
        _firstResponse: false,
        _progeressId: 16, // 可以根据需要生成唯一的进度ID
        status: "added", // 添加状态属性
      };
      // this.onFileAdded(customFileObject)
      // 触发 uploader 的 file-added 事件
      this.uploader.addNode(customFileObject.file);
    },
    generateUniqueIdentifier(file) {
      // 使用文件名和时间戳生成唯一的标识符
      const timestamp = Date.now();
      return `${file.name}-${timestamp}`;
    },
    onFileAdded(file) {
      this.panelShow = false;
      this.computeMD5(file);
      // // 触发 uploader 的 file-added 事件
      // this.uploader.addNode(file.file);
      this.$EventBus.$emit("fileAdded");
    },
    //上传过程中，会不断触发file-progress上传进度的回调
    onFileProgress(rootFile, file, chunk) {
      console.log(
        `分片上传中 ${file.name}，chunk：${chunk.endByte / 1024 / 1024} ~ ${file.size / 1024 / 1024
        }`
      );
      this.str = `分片上传中 ${file.name} ${Math.round(chunk.endByte / file.size * 100)} %`;
    },
    onFileSuccess(rootFile, file, response, chunk) {
      let res = JSON.parse(response);

      // TODO 如有需要 解开注释 和后台协议如何处理这种情况:服务器自定义的错误（即虽返回200，但是是错误的情况），这种错误是Uploader无法拦截的
      // if (!res.result) {
      //   this.$message({message: res.message, type: 'error'});
      //   // 文件状态设为“失败”
      //   this.statusSet(file.id, 'failed');
      //   return
      // }

      // 如果服务端返回需要合并
      if (res.needMerge) {
        this.str = `上传成功合并中`;
        // 文件状态设为“合并中”
        this.statusSet(file.id, "merging");
        let param = {
          filename: rootFile.name,
          identifier: rootFile.uniqueIdentifier,
          totalSize: rootFile.size,
          prefixFlag: this.params.prefixFlag,
          userId: this.params.userId,
        };
        mergeFile(param)
          .then((res) => {
            // 文件合并成功
            this.$EventBus.$emit("fileSuccess", res);
            this.str = "上传成功"
            this.statusRemove(file.id);
            this.show = false;
          })
          .catch((e) => {
            console.log("合并异常,重新发起请求,文件名为:", file.name);
            //由于网络或服务器原因,导致合并过程中断线,此时如果不重新发起请求,就会进入失败的状态,导致该文件无法重试
            file.retry();
          });

        // 不需要合并
      } else {
        this.$EventBus.$emit("fileSuccess", res);
      }
    },
    onFileError(rootFile, file, response, chunk) {
      this.$message({
        message: response,
        type: "error",
      });
    },

    /**
     * 计算md5，实现断点续传及秒传
     * @param file
     */
    computeMD5(file) {
      let _this = this;
      let fileReader = new FileReader();
      let time = new Date().getTime();
      let blobSlice =
        File.prototype.slice ||
        File.prototype.mozSlice ||
        File.prototype.webkitSlice;
      let currentChunk = 0;
      const chunkSize = 50 * 1024 * 1000;
      let chunks = Math.ceil(file.size / chunkSize);
      let spark = new SparkMD5.ArrayBuffer();

      // 文件状态设为"计算MD5"
      this.statusSet(file.id, "md5");
      file.pause();

      loadNext();

      fileReader.onload = (e) => {
        spark.append(e.target.result);

        if (currentChunk < chunks) {
          currentChunk++;
          loadNext();

          // 实时展示MD5的计算进度
          _this.$nextTick(() => {
            $(`.myStatus_${file.id}`).text(
              "校验MD5 " + ((currentChunk / chunks) * 100).toFixed(0) + "%"
            );
          });
        } else {
          let md5 = spark.end();
          _this.computeMD5Success(md5, file);
          console.log(
            `MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size
            } 用时：${new Date().getTime() - time} ms`
          );
        }
      };

      fileReader.onerror = function () {
        _this.error(`文件${file.name}读取出错，请检查该文件`);
        file.cancel();
      };

      function loadNext() {
        let start = currentChunk * chunkSize;
        let end =
          start + chunkSize >= file.size ? file.size : start + chunkSize;

        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
      }
    },

    computeMD5Success(md5, file) {
      // 将自定义参数直接加载uploader实例的opts上
      Object.assign(this.uploader.opts, {
        query: {
          ...this.params,
        },
      });

      file.uniqueIdentifier = md5;
      file.resume();
      this.statusRemove(file.id);
    },

    fileListShow() {
      let $list = $("#global-uploader .file-list");

      if ($list.is(":visible")) {
        $list.slideUp();
        this.collapse = true;
      } else {
        $list.slideDown();
        this.collapse = false;
      }
    },
    close() {
      this.uploader.cancel();

      this.panelShow = false;
    },

    /**
     * 新增的自定义的状态: 'md5'、'transcoding'、'failed'
     * @param id
     * @param status
     */
    statusSet(id, status) {
      let statusMap = {
        md5: {
          text: "校验MD5",
          bgc: "#00556f",
        },
        merging: {
          text: "合并中",
          bgc: "#0084ad",
        },
        transcoding: {
          text: "转码中",
          bgc: "#0084ad",
        },
        failed: {
          text: "上传失败",
          bgc: "#0084ad",
        },
        success: {
          text: "上传成功",
          bgc: "#0084ad",
        },
      };

      this.$nextTick(() => {
        $(`<p class="myStatus_${id}"></p>`)
          .appendTo(`.file_${id} .uploader-file-status`)
          .css({
            position: "absolute",
            top: "0",
            left: "0",
            right: "0",
            bottom: "0",
            zIndex: "1",
            "line-height": "initial",
            backgroundColor: statusMap[status].bgc,
          })
          .text(statusMap[status].text);
      });
    },
    statusRemove(id) {
      this.$nextTick(() => {
        $(`.myStatus_${id}`).remove();
      });
    },

    error(msg) {
      this.$notify({
        title: "错误",
        message: msg,
        type: "error",
        duration: 2000,
      });
    },
  },
  watch: {},
  destroyed() {
    this.$EventBus.$off("openUploader");
    this.$EventBus.$off("close");
  },
  components: {},
};
</script>

<style scoped lang="scss">
#global-uploader {
  // display: none;
  position: fixed;
  z-index: 9999;
  right: 15px;
  bottom: 15px;

  h1,
  h2 {
    font-weight: normal;
  }

  ul {
    list-style-type: none;
    padding: 0px 4px;
  }

  li {
    display: inline-block;
  }

  .uploader-app {
    width: 660px;
  }

  .file-panel {
    background-color: #004472;
    color: #00cad5;
    border: 1px solid #3a779f;
    border-radius: 7px 7px 0 0;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);

    .file-title {
      display: flex;
      height: 40px;
      line-height: 0px;
      padding: 0 15px;
      border-bottom: 1px solid #3a779f;

      .operate {
        flex: 1;
        text-align: right;
      }
    }

    .file-list {
      position: relative;
      height: 264px;
      width: 654px;
      overflow-x: hidden;
      overflow-y: auto;
      background-color: #004472;

      >li {
        background-color: #004472;
      }
    }

    &.collapse {
      .file-title {
        background-color: #006696;
      }
    }
  }

  .no-file {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 16px;
  }

  .uploader-file {
    width: 654px;
  }

  ///deep/ .uploader-file-icon {
  .uploader-file-icon {
    &:before {
      content: "" !important;
    }

    &[icon="image"] {
      background: url("/images/png/image-icon.png");
    }

    &[icon="video"] {
      background: url("/images/png/video-icon.png");
    }

    &[icon="document"] {
      background: url("/images/png/text-icon.png");
    }
  }

  ///deep/ .uploader-file-actions > span {
  .uploader-file-actions>span {
    margin-right: 6px;
  }
}

/* 隐藏上传按钮 */
#global-uploader-btn {
  position: absolute;
  clip: rect(0, 0, 0, 0);
}

.fff {
  color: #fff !important;
}

/*.uploader-list>ul>li{*/
/*  width:100%;*/
/*  color:red;*/
/*  margin-bottom: 0;*/
/*}*/
/*a {*/
/*  color: #42b983;*/
/*}*/
</style>